package com.zzyl.nursing.service.impl;

import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * 报警规则Service业务层处理
 * 
 * @author ruoyi
 * @date 2024-10-19
 */
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private IAlertDataService alertDataService;

    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }

    /**
     * 报警规则过滤
     */
    @Override
    public void alertFilter() {
        // 查询有没有有效的规则，如果没有，直接结束
        long count = count(Wrappers.<AlertRule>lambdaQuery().eq(AlertRule::getStatus, 1));
        if (count <= 0) {
            return;
        }
        // 到redis中查询所有设备上报的数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICEDATA_LATEST);

        // 判断数据是否为空，为空直接结束
        if (CollUtil.isEmpty(values)) {
            return;
        }

        // 解析拿到的所有上报数据，并存入一个新的集合中
        ArrayList<DeviceData> deviceDataList = new ArrayList<>();
        values.forEach(item -> {
            List<DeviceData> list = JSONUtil.toList((String) item, DeviceData.class);
            // 将单个list集合存入最终要汇总数据的集合中
//            deviceDataList.addAll(list);
            CollUtil.addAll(deviceDataList, list);
        });

        // 遍历拿到的IOT上报的所有数据，交给数据报警处理方法处理
        deviceDataList.forEach(deviceData -> {
            alertDataHandle(deviceData);
        });
    }

    /**
     * 逐条处理报警数据
     * @param deviceData    单条报警数据
     */
    private void alertDataHandle(DeviceData deviceData) {
        // 获取所有报警规则
        // 第一类：该产品下所有物模型下的所有规则
        List<AlertRule> iotRules = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, deviceData.getIotId()));

        // 第二类：该产品下所有指定设备对应的物模型的规则
        List<AlertRule> commonRules = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, -1));

        // 合并两类规则，并判断
        iotRules.addAll(commonRules);

        // 判断规则是否为空
        if (CollUtil.isEmpty(iotRules)) {
            return;
        }

        // 规则不为空，遍历每一条规则，判断上报的数据是否能匹配规则
        iotRules.forEach(rule -> {
            deviceDataAlarmHandler(rule, deviceData);
        });
    }

    /**
     * 判断数据是否能匹配到规则
     * @param rule  规则
     * @param deviceData    设备数据
     */
    private void deviceDataAlarmHandler(AlertRule rule, DeviceData deviceData) {
        // 1.判断数据是否在生效时间内
        LocalTime alertTime = deviceData.getAlarmTime().toLocalTime();
        // 00:00:00~23:59:59
        String[] dates = rule.getAlertEffectivePeriod().split("~");
        LocalTime beginTime = LocalTime.parse(dates[0]);
        LocalTime endTime = LocalTime.parse(dates[1]);

        // 如果不在生效时间内，直接返回
        if (alertTime.isBefore(beginTime) || alertTime.isAfter(endTime)) {
            return;
        }

        // 2.判断数据是否达到阈值
        Double dataValue = Double.valueOf(deviceData.getDataValue());   // 数据值
        Double value = rule.getValue(); // 报警阈值
        String operator = rule.getOperator();   // 运算符： >=  <

        String alertCountKey = CacheConstants.IOT_ALERT_COUNT + rule.getProductKey() + ":" + deviceData.getIotId() + ":" + rule.getFunctionId() + ":" + rule.getId();

        // NumberUtil.compare：如果前面比后面大，返回正整数，否则返回负整数
        int compare = NumberUtil.compare(dataValue, value);
        // 心率低于60报警
        // 判断未达到阈值
        if (Objects.equals(operator, ">=") && compare < 0 || Objects.equals(operator, "<") && compare >= 0) {
            // 没有达到阈值，删除redis中的报警数
            redisTemplate.delete(alertCountKey);
            return;
        }

        // 当前这条数据达到了报警阈值，查询redis中的沉默周期，判断是否为空
        // 沉默周期的redis键的设计：iot:alert_silent:productKey:iotId:functionId:ruleId
        String silentKey = CacheConstants.IOT_ALERT_SILENT + rule.getProductKey() + ":" + deviceData.getIotId() + ":" + rule.getFunctionId() + ":" + rule.getId();
        String silentValue = redisTemplate.opsForValue().get(silentKey);
        if (StringUtils.isNotEmpty(silentValue)) {
            // 在沉默周期内，结束
            return;
        }
        // 不在沉默周期内，继续往下走，判断持续周期
        // 从redis中获取一个已报警次数
        String countStr = redisTemplate.opsForValue().get(alertCountKey);
        if (StringUtils.isEmpty(countStr)) {
            countStr = "0";
        }

        Integer alertCount = Integer.valueOf(countStr);

        // 先将报警数+1，再判断是否达到了持续周期
        alertCount++;
        if (alertCount < rule.getDuration()) {
            // 未达到持续周期，将redis中的报警数+1，结束
            redisTemplate.opsForValue().set(alertCountKey, alertCount.toString());
            return;
        }

        // 达到持续周期，发送报警消息，将redis中的报警数置为0，结束
        redisTemplate.delete(alertCountKey);
        // 添加redis沉默周期
        redisTemplate.opsForValue().set(silentKey, "1", rule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        // 判断当前数据是否是老人数据
        List<Long> alertUserIds = getAlertUserIds(rule, deviceData);

        // 调用方法，批量保存报警数据
        List<AlertData> alertDataList = batchSaveAlertData(rule, deviceData, alertUserIds);

        // 使用WebSocket推送消息到客户端
        sendMessageToClient(alertDataList, alertUserIds, rule);
    }

    /**
     * 将报警数据推送到客户端
     *
     * @param alertDataList 所有报警数据
     * @param alertUserIds  需要接收消息的人的列表
     * @param rule 报警规则
     */
    private void sendMessageToClient(List<AlertData> alertDataList, List<Long> alertUserIds, AlertRule rule) {
        if (CollUtil.isEmpty(alertDataList)) {
            return;
        }
        AlertData alertData = alertDataList.get(0);
        AlertNotifyVo alertNotifyVo = BeanUtil.toBean(alertData, AlertNotifyVo.class);
        alertNotifyVo.setAlertDataType(alertData.getType());
        alertNotifyVo.setNotifyType(1);
        alertNotifyVo.setFunctionName(rule.getFunctionName());

        webSocketServer.sendMessageToConsumer(alertNotifyVo, alertUserIds);
    }

    /**
     * 获取所有需要接收报警数据的人的id
     * @param rule      报警规则
     * @param deviceData    设备数据
     * @return  接收人集合
     */
    private List<Long> getAlertUserIds(AlertRule rule, DeviceData deviceData) {
        List<Long> userIds = new ArrayList<>();
        if (Objects.equals(rule.getAlertDataType(), 0)) {
            // 是老人数据
            // 判断是否是固定设备
            if (Objects.equals(deviceData.getLocationType(), 1)
                    && Objects.equals(deviceData.getPhysicalLocationType(), 2)) {
                userIds = deviceMapper.selectNursingIdsByIotIdWithBed(deviceData.getIotId());
            }
            // 判断是否是随身设备
            if (Objects.equals(deviceData.getLocationType(), 0)) {
                userIds = deviceMapper.selectNursingIdsByIotIdWithElder(deviceData.getIotId());
            }
        } else {
            // 设备异常数据，查询维修人员
            userIds = sysUserRoleMapper.selectUserIdByRoleName("维修工");
        }

        // 不论是哪一类数据，都要查询超级管理员，发送通知
        List<Long> managerIds = sysUserRoleMapper.selectUserIdByRoleName("超级管理员");

        // 合并管理员和用户
        return (List<Long>) CollUtil.addAll(userIds, managerIds);
    }

    /**
     * 批量保存报警数据
     * @param rule      报警规则
     * @param deviceData    设备要报警的数据
     * @param alertUserIds  要接收报警的人的id
     */
    private List<AlertData> batchSaveAlertData(AlertRule rule, DeviceData deviceData, List<Long> alertUserIds) {
        List<AlertData> alertDataList = new ArrayList<>();

        // 遍历接收人的集合
        alertUserIds.forEach(alertUserId -> {
            // 属性拷贝
            AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
            // 补全alertData的属性
            alertData.setId(null);
            // 报警原因，格式：功能名称+运算符+阈值+持续周期+聚合周期
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期报警", rule.getFunctionName(), rule.getOperator(), rule.getValue(), rule.getDuration());
            alertData.setAlertReason(alertReason);
            alertData.setType(rule.getAlertDataType());
            alertData.setAlertRuleId(rule.getId());
            alertData.setStatus(0);
            // 设置这条报警数据的通知人id
            alertData.setUserId(alertUserId);
            alertDataList.add(alertData);
        });

        alertDataService.saveBatch(alertDataList);
        return alertDataList;
    }
}
